package com.ting.encrypt.interceptor;

import com.ting.common.enums.ResultEnum;
import com.ting.common.exception.TingBaseException;
import com.ting.encrypt.annotation.EncryptField;
import com.ting.encrypt.annotation.SensitiveEncryptEnabled;
import com.ting.encrypt.encrypt.BaseEncrypt;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.*;

@Intercepts({@Signature(
        type = ResultSetHandler.class,
        method = "handleResultSets",
        args = {Statement.class}
)})
@Slf4j
@Component
public class DecryptInterceptor implements Interceptor {

    private static final String MAPPED_STATEMENT = "mappedStatement";

    @Autowired
    ApplicationContext applicationContext;

    @Value("${encrypt.enable:false}")
    private boolean encryptEnable;


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        List<Object> results = (List)invocation.proceed();
        if (CollectionUtils.isEmpty(results)) {
            return results;
        }
        if (!encryptEnable){
            return results;
        }
        ResultSetHandler resultSetHandler = (ResultSetHandler)invocation.getTarget();
        MetaObject metaObject = SystemMetaObject.forObject(resultSetHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue(MAPPED_STATEMENT);
        ResultMap resultMap =
                mappedStatement.getResultMaps().isEmpty()
                        ? null
                        : mappedStatement.getResultMaps().get(0);
        // 如果不需要解密，直接返回
        Object reuslt0 = results.get(0);
        if(reuslt0 == null) return null;
        SensitiveEncryptEnabled annotation =
                reuslt0.getClass().getAnnotation(SensitiveEncryptEnabled.class);
        if (annotation == null) {
            return results;
        }
        Class<?> resultMapType = resultMap.getType();
        List<Field> declaredFields = getClassFields(resultMapType);
        Map<String, EncryptField> encryptFieldMap = new HashMap<>();

        for (Field field : declaredFields) {
            EncryptField encryptField = field.getAnnotation(EncryptField.class);
            if (encryptField != null) {
                encryptFieldMap.put(field.getName(), encryptField);
            }
        }
        for (Object result : results) {
            final MetaObject objMetaObject =
                    mappedStatement.getConfiguration().newMetaObject(result);
            decryptValue(encryptFieldMap, objMetaObject);
        }
        //    long l2 = System.currentTimeMillis();
        //    System.out.println("decrypt cost time:"+(l2-l));
        return results;

    }

    private List<Field> getClassFields(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            if (clazz.equals(Object.class)) {
                clazz = clazz.getSuperclass();
                continue;
            }
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fieldList;
    }

    private void decryptValue(Map<String, EncryptField> encryptFieldMap, MetaObject objMetaObject) {
        for (Map.Entry<String, EncryptField> entry : encryptFieldMap.entrySet()) {
            String key = entry.getKey();
            Object value = objMetaObject.getValue(key);
            EncryptField encryptField = entry.getValue();
            Class<? extends BaseEncrypt> encryptClass = encryptField.encryptType();
            try {
                BaseEncrypt baseEncrypt = applicationContext.getBean(encryptClass);
                if(baseEncrypt == null ) {
                    baseEncrypt = encryptClass.newInstance();
                }
                String decryptValue;
//                // 如果是json字符串
//                if (encryptField.isJsonField()) {
//                    decryptValue =
//                            JsonUtil.operatorKeyword(
//                                    value,
//                                    baseEncrypt,
//                                    encryptField.jsonFieldNames(),
//                                    OperateType.DECRYPT);
//                } else {
                    decryptValue = baseEncrypt.decrypt((String) value);
//                }
                objMetaObject.setValue(key, decryptValue);
            } catch (Exception e) {
                log.error("数据加密解密失败", e);
                throw new TingBaseException(ResultEnum.DECRYPT_ERROR);
            }
        }
    }
}
